package cn.hutool.core.util;

import cn.hutool.core.io.file.InMemoryFile;

import java.io.*;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;


/**
 * 基于内存管理文件的导入导出
 * @author 蓝大叔
 */
public class MemoryFileUtil {

    private final InMemoryFile root;

    public MemoryFileUtil(String rootPath) {
        this.root = new InMemoryFile(rootPath, true);
    }

    public InMemoryFile createMemory(String path, boolean isDirectory) {
        String[] parts = path.split("/");
        InMemoryFile current = root;

        for (int i = 1; i < parts.length; i++) {
            String part = parts[i];
            if (i == parts.length - 1) {
                InMemoryFile newFile = new InMemoryFile(part, isDirectory);
                current.addChild(newFile);
                return newFile;
            } else {
                InMemoryFile next = current.getChildren().get(part);
                if (next == null) {
                    next = new InMemoryFile(part, true);
                    current.addChild(next);
                }
                current = next;
            }
        }
        return current;
    }

    public void writeMemory(String path, byte[] content) {
        InMemoryFile file = createMemory(path, false);
        file.setContent(content);
    }

    /**
     * 写入文件内容，如果文件存在则直接写入，不存在则创建
     * 如果你确定路径已存在，使用 writeFile(path, content, false) 来获得更好的性能
     * 如果你不确定路径是否存在，使用原始的 writeFile(path, content) 方法
     * @return 是否是新创建的文件
     */
    public boolean writeMemory(String path, byte[] content, boolean createIfNotExist) {
        InMemoryFile file = findFile(path);
        if (file == null) {
            if (createIfNotExist) {
                // 文件不存在且需要创建
                file = createMemory(path, false);
                file.setContent(content);
                return true;
            } else {
                throw new IllegalStateException("File not found: " + path);
            }
        } else {
            // 文件存在，检查是否是目录
            if (file.isDirectory()) {
                throw new IllegalStateException("Cannot write content to a directory: " + path);
            }
            // 直接写入内容
            file.setContent(content);
            return false;
        }
    }

    /**
     * 根据路径导出文件系统到 ZIP 文件
     * @param zipFilePath
     * @throws IOException
     */
    public void exportToZip(String zipFilePath) throws IOException {
        try (FileOutputStream fos = new FileOutputStream(zipFilePath);
             ZipOutputStream zos = new ZipOutputStream(fos)) {
            exportDirectoryToZip(root, "", zos);
        }
    }

    /**
     * 检查文件是否存在
     */
    public boolean exists(String path) {
        return findFile(path) != null;
    }

    /**
     * 获取文件
     */
    public InMemoryFile getFile(String path) {
        InMemoryFile file = findFile(path);
        if (file == null) {
            throw new IllegalStateException("File not found: " + path);
        }
        return file;
    }

    private void exportDirectoryToZip(InMemoryFile file, String parentPath, ZipOutputStream zos) throws IOException {
        String currentPath = parentPath + file.getName();
        if (file.isDirectory()) {
            currentPath = currentPath.endsWith("/") ? currentPath : currentPath + "/";
            if (!currentPath.equals("/")) {
                ZipEntry dirEntry = new ZipEntry(currentPath);
                zos.putNextEntry(dirEntry);
                zos.closeEntry();
            }
            if (file.getChildren() != null) {
                for (InMemoryFile child : file.getChildren().values()) {
                    exportDirectoryToZip(child, currentPath, zos);
                }
            }
        } else {
            ZipEntry fileEntry = new ZipEntry(currentPath);
            zos.putNextEntry(fileEntry);
            if (file.getContent() != null) {
                zos.write(file.getContent());
            }
            zos.closeEntry();
        }
    }

    public void importFromZip(String zipFilePath) throws IOException {
        try (ZipFile zipFile = new ZipFile(zipFilePath)) {
            Enumeration<? extends ZipEntry> entries = zipFile.entries();

            while (entries.hasMoreElements()) {
                ZipEntry entry = entries.nextElement();
                String path = entry.getName();

                if (entry.isDirectory()) {
                    createMemory(path, true);
                } else {
                    try (InputStream is = zipFile.getInputStream(entry)) {
                        // 使用 ByteArrayOutputStream 替代 readAllBytes()
                        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
                        int nRead;
                        byte[] data = new byte[1024];
                        while ((nRead = is.read(data, 0, data.length)) != -1) {
                            buffer.write(data, 0, nRead);
                        }
                        buffer.flush();
                        writeMemory(path, buffer.toByteArray());
                    }
                }
            }
        }
    }

    public void listFiles(String path) {
		if(StrUtil.isBlank(path)){
			path = "/";
		}
        InMemoryFile current = findFile(path);
        if (current != null && current.isDirectory()) {
            printDirectory(current, 0);
        }
    }

    private void printDirectory(InMemoryFile dir, int level) {
        StringBuilder indent = new StringBuilder();
        for (int i = 0; i < level; i++) {
            indent.append("  ");
        }
        String dirName = dir.getName();
        if (!dirName.equals("./") && !dirName.equals("/")) {
            System.out.println(indent + dirName + "/");
        }
        if (dir.getChildren() != null) {
            dir.getChildren().values().forEach(child -> {
                if (child.isDirectory()) {
                    printDirectory(child, level + (dirName.equals("./") || dirName.equals("/") ? 0 : 1));
                } else {
                    String childIndent = (dirName.equals("./") || dirName.equals("/")) ? indent.toString() : indent + "  ";
                    System.out.println(childIndent + child.getName());
                }
            });
        }
    }

    private InMemoryFile findFile(String path) {
        if ("/".equals(path)){
            return root;
        }
        String[] parts = path.split("/");
        InMemoryFile current = root;
        for (int i = 1; i < parts.length; i++) {
            current = current.getChildren().get(parts[i]);
            if (current == null){
                return null;
            }
        }
        return current;
    }

    public void exportToDirectory(String rootPath) throws IOException {
        File directory;
        if (rootPath == null) {
            // 如果路径为空，使用当前文件系统的根路径结构
            directory = new File(root.getName());
        } else {
            directory = new File(rootPath);
        }

        if (!directory.exists()) {
            directory.mkdirs();
        }
        exportDirectoryToFS(root, directory);
    }

    private void exportDirectoryToFS(InMemoryFile file, File parentDir) throws IOException {
        // 对根目录做特殊处理
        File currentFile;
        if (file.getName().equals("/")) {
            currentFile = parentDir;
        } else {
            currentFile = new File(parentDir, file.getName());
        }

        if (file.isDirectory()) {
            currentFile.mkdir();
            if (file.getChildren() != null) {
                for (InMemoryFile child : file.getChildren().values()) {
                    exportDirectoryToFS(child, currentFile);
                }
            }
        } else {
            try (FileOutputStream fos = new FileOutputStream(currentFile)) {
                if (file.getContent() != null) {
                    fos.write(file.getContent());
                }
            }
        }
    }

    public void exportToFile(String memoryPath, String targetFilePath) throws IOException {
        // 查找内存中的文件
        InMemoryFile file = findFile(memoryPath);
        if (file == null) {
            throw new IllegalArgumentException("File not found in memory: " + memoryPath);
        }
        // 确保是文件而不是目录
        if (file.isDirectory()) {
            throw new IllegalArgumentException("Cannot export directory as file: " + memoryPath);
        }
        // 导出文件内容
        try (FileOutputStream fos = new FileOutputStream(targetFilePath)) {
            if (file.getContent() != null) {
                fos.write(file.getContent());
            }
        }
    }

    private Map<String, Object> serializeFileSystem(InMemoryFile file) {
        Map<String, Object> fileMap = new HashMap<>();
        fileMap.put("name", file.getName());
        fileMap.put("isDirectory", file.isDirectory());

        if (file.isDirectory()) {
            Map<String, Object> childrenMap = new HashMap<>();
            for (Map.Entry<String, InMemoryFile> entry : file.getChildren().entrySet()) {
                childrenMap.put(entry.getKey(), serializeFileSystem(entry.getValue()));
            }
            fileMap.put("children", childrenMap);
        } else {
            fileMap.put("content", file.getContent());
        }

        return fileMap;
    }

    public void importFromDirectory(String directoryPath) throws IOException {
        File directory = new File(directoryPath);
        if (!directory.exists() || !directory.isDirectory()) {
            throw new IllegalArgumentException("Invalid directory path: " + directoryPath);
        }
        importDirectoryFromFS(directory, "/");
    }

    private void importDirectoryFromFS(File file, String path) throws IOException {
        if (file.isDirectory()) {
            createMemory(path + file.getName(), true);
            File[] children = file.listFiles();
            if (children != null) {
                for (File child : children) {
                    importDirectoryFromFS(child, path + file.getName() + "/");
                }
            }
        } else {
            try (FileInputStream fis = new FileInputStream(file);
                 ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
                byte[] buffer = new byte[4096];
                int bytesRead;
                while ((bytesRead = fis.read(buffer)) != -1) {
                    bos.write(buffer, 0, bytesRead);
                }
                writeMemory(path + file.getName(), bos.toByteArray());
            }
        }
    }

    @SuppressWarnings("unchecked")
    public void importFromFile(String sourceFilePath, String memoryPath, String targetFileName) throws IOException {
        // 读取源文件内容
        byte[] content;
        try (FileInputStream fis = new FileInputStream(sourceFilePath);
             ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = fis.read(buffer)) != -1) {
                bos.write(buffer, 0, bytesRead);
            }
            content = bos.toByteArray();
        }

        // 确定目标路径
        String targetPath;
        if (memoryPath == null) {
            // 如果目标路径为空，则放在根目录下
            targetPath = "/" + (targetFileName != null ? targetFileName : new File(sourceFilePath).getName());
        } else {
            // 确保目标路径以 / 结尾
            String normalizedPath = memoryPath.endsWith("/") ? memoryPath : memoryPath + "/";
            targetPath = normalizedPath + (targetFileName != null ? targetFileName : new File(sourceFilePath).getName());
        }

        // 写入到内存文件系统
        writeMemory(targetPath, content);
    }

    // 原有方法调用新方法，保持向后兼容
    @SuppressWarnings("unchecked")
    public void importFromFile(String sourceFilePath, String memoryPath) throws IOException {
        importFromFile(sourceFilePath, memoryPath, null);
    }

    private void deserializeFileSystem(Map<String, Object> fileMap, String parentPath) {
        String name = (String) fileMap.get("name");
        boolean isDirectory = (boolean) fileMap.get("isDirectory");
        String currentPath = parentPath + name;

        if (isDirectory) {
            createMemory(currentPath, true);
            Map<String, Object> children = (Map<String, Object>) fileMap.get("children");
            for (Map.Entry<String, Object> entry : children.entrySet()) {
                deserializeFileSystem((Map<String, Object>) entry.getValue(), currentPath + "/");
            }
        } else {
            byte[] content = (byte[]) fileMap.get("content");
            writeMemory(currentPath, content);
        }
    }

    public void clearMemory() {
        root.getChildren().clear();
    }
}
